iT邦幫忙

2021 iThome 鐵人賽

DAY 22
0

Model 資料層

在開發應用程式的過程裡,我們通常會定義 Model 的類別用來處理資料結構或是資料儲存上的使用。

舉個簡單的例子,定義一個 JSON { "name": "Leo" } 資料的 Model 類別

class User {
    User({
        required this.name,
    });

    final String name;

    factory User.fromJson(Map<String, dynamic> json) => User(
        name: json["name"],
    );

    Map<String, dynamic> toJson() => {
        "name": name,
    };
}


void main() {
  var userData = { "name": "Leo" };
  var user = User.fromJson(userData);
  print(user.toJson().toString());  // {name: Leo}
}

為什麼需要 Model 類別來處理資料?

  • 資料的安全性:在開發的過程我們雖然也可以從 Map 型別取資料,但是無法掌握 value 對應的真實型別

  • 資料的管理與維護:隨著專案大小,定義清楚的 Model 類別有助於開發與維護的工作進行

我們來看看氣象資料開放平臺 API 回傳的 json 資料結構大致如下,可以看到我們我們至少就有5層的 Model 需要定義

{
    "success": "true",
    "records": {
        "locations": [{
            "datasetDescription": "臺灣各縣市鄉鎮未來3天(72小時)逐3小時天氣預報",
            "locationsName": "臺北市",
            "dataid": "D0047-061",
            "location": [{
                "locationName": "中正區",
                "geocode": "63000050",
                "lat": "25.046058",
                "lon": "121.516565",
                "weatherElement": [{
                    "elementName": "Wx",
                    "description": "天氣現象",
                    "time": [{
                        "startTime": "2021-09-16 06:00:00",
                        "endTime": "2021-09-16 09:00:00",
                        "elementValue": [{
                                "value": "短暫陣雨或雷雨",
                                "measures": "自定義 Wx 文字"
                            },
                            {
                                "value": "15",
                                "measures": "自定義 Wx 單位"
                            }
                        ]
                    }]
                }]
            }]
        }]
    }
}

這個 API 回傳的資料結構包含了某地區、某時間、某天氣因子、某單位…,在取用資料上相對麻煩,

我們可以透過 Model 類別來處理回傳的資料格式,並定義一些方法來取得資料內容,如下:

    var data = await WetherAPI().fetch(service, parameters: params);

    // json_serializable
    var weather = WeatherModel.fromJson(data);

    // first 自定義屬性用來取得第一筆
    var record = weather.records.first;

    // 自定義 element 方法用來取得天氣因子
    var description = record.element("WeatherDescription").now.values[0].value;

    var wx = record.element("Wx").now.values[1].value;

建構 Model 工具

簡單的資料結構我們可以手動寫 Code 處理,不過實務上的資料結構通常比較複雜,我們可以使用一些工具協助創建 Model 類別,將開發時間留給業務邏輯的處理而不是資料的建模。

  • json_serializable - 需手動定義好資料的結構,可以自動產生序列化相關的程式碼。

  • quicktype - 提供 json 的資料內容,會自動化產生 Model 的程式碼,需檢查上下內容是否符合自己預期。

氣象 API 資料

透過自動化的轉換,我們使用 quicktype 提供 json 格式自動化產生對應的 Model

  • WeatherAPIResponse
  • Records
  • RecordsLocation
  • LocationLocation
  • WeatherElement
  • Time
  • ElementValue

完整程式碼如下:

// To parse this JSON data, do
//
//     final weatherApiResponse = weatherApiResponseFromJson(jsonString);

import 'package:meta/meta.dart';
import 'dart:convert';

WeatherApiResponse weatherApiResponseFromJson(String str) => WeatherApiResponse.fromJson(json.decode(str));

String weatherApiResponseToJson(WeatherApiResponse data) => json.encode(data.toJson());

class WeatherApiResponse {
    WeatherApiResponse({
        required this.success,
        required this.records,
    });

    final String success;
    final Records records;

    factory WeatherApiResponse.fromJson(Map<String, dynamic> json) => WeatherApiResponse(
        success: json["success"],
        records: Records.fromJson(json["records"]),
    );

    Map<String, dynamic> toJson() => {
        "success": success,
        "records": records.toJson(),
    };
}

class Records {
    Records({
        required this.locations,
    });

    final List<RecordsLocation> locations;

    factory Records.fromJson(Map<String, dynamic> json) => Records(
        locations: List<RecordsLocation>.from(json["locations"].map((x) => RecordsLocation.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "locations": List<dynamic>.from(locations.map((x) => x.toJson())),
    };
}

class RecordsLocation {
    RecordsLocation({
        required this.datasetDescription,
        required this.locationsName,
        required this.dataid,
        required this.location,
    });

    final String datasetDescription;
    final String locationsName;
    final String dataid;
    final List<LocationLocation> location;

    factory RecordsLocation.fromJson(Map<String, dynamic> json) => RecordsLocation(
        datasetDescription: json["datasetDescription"],
        locationsName: json["locationsName"],
        dataid: json["dataid"],
        location: List<LocationLocation>.from(json["location"].map((x) => LocationLocation.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "datasetDescription": datasetDescription,
        "locationsName": locationsName,
        "dataid": dataid,
        "location": List<dynamic>.from(location.map((x) => x.toJson())),
    };
}

class LocationLocation {
    LocationLocation({
        required this.locationName,
        required this.geocode,
        required this.lat,
        required this.lon,
        required this.weatherElement,
    });

    final String locationName;
    final String geocode;
    final String lat;
    final String lon;
    final List<WeatherElement> weatherElement;

    factory LocationLocation.fromJson(Map<String, dynamic> json) => LocationLocation(
        locationName: json["locationName"],
        geocode: json["geocode"],
        lat: json["lat"],
        lon: json["lon"],
        weatherElement: List<WeatherElement>.from(json["weatherElement"].map((x) => WeatherElement.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "locationName": locationName,
        "geocode": geocode,
        "lat": lat,
        "lon": lon,
        "weatherElement": List<dynamic>.from(weatherElement.map((x) => x.toJson())),
    };
}

class WeatherElement {
    WeatherElement({
        required this.elementName,
        required this.description,
        required this.time,
    });

    final String elementName;
    final String description;
    final List<Time> time;

    factory WeatherElement.fromJson(Map<String, dynamic> json) => WeatherElement(
        elementName: json["elementName"],
        description: json["description"],
        time: List<Time>.from(json["time"].map((x) => Time.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "elementName": elementName,
        "description": description,
        "time": List<dynamic>.from(time.map((x) => x.toJson())),
    };
}

class Time {
    Time({
        required this.startTime,
        required this.endTime,
        required this.elementValue,
    });

    final DateTime startTime;
    final DateTime endTime;
    final List<ElementValue> elementValue;

    factory Time.fromJson(Map<String, dynamic> json) => Time(
        startTime: DateTime.parse(json["startTime"]),
        endTime: DateTime.parse(json["endTime"]),
        elementValue: List<ElementValue>.from(json["elementValue"].map((x) => ElementValue.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "startTime": startTime.toIso8601String(),
        "endTime": endTime.toIso8601String(),
        "elementValue": List<dynamic>.from(elementValue.map((x) => x.toJson())),
    };
}

class ElementValue {
    ElementValue({
        required this.value,
        required this.measures,
    });

    final String value;
    final String measures;

    factory ElementValue.fromJson(Map<String, dynamic> json) => ElementValue(
        value: json["value"],
        measures: json["measures"],
    );

    Map<String, dynamic> toJson() => {
        "value": value,
        "measures": measures,
    };
}

今日成果

程式碼

weather


上一篇
Flutter體驗 Day 21-Http
下一篇
Flutter體驗 Day 23-WebSocket
系列文
Flutter / Dart 跨平台App開發體驗30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言